/**
 * 1. 实现 observer思路：
 * 我们知道Object.defineProperty的特性了，
 * 我们就利用它的set和get。
 * 我们将要observe的对象，通过递归，
 * 将它所有的属性，包括子属性的属性，
 * 都给加上set和get。这样的话，
 * 给这个对象的某个属性赋值，就会触发set
 */
class Observer{
	constructor(value){//假设我们传入的值是一个对象
		this.value = value;
		this.walk(value);//递归调用walk函数使得value的属性都有set方法
	}
	walk(value){
		Object.keys(value).forEach(key=>this.convert(key,value[key]))
	}
	convert(key,val){
		defineReactive(this.value,key,val);
	}
}
function defineReactive(obj,key,val){
	//检查val是否是一对象
	var childOb = observe(val);
	var dep = new Dep();//实例化订阅类
	Object.defineProperty(obj,key,{
		enumberable : true,//表示可以让for-in循环和Object.keys()遍历属性
		configurable : true,//表示可以配置属性的，是一个总开关
		// get : ()=>val;//返回val
		get : ()=>{
			 // 说明这是watch 引起的
			if(Dep.target){
				dep.addSub(Dep.target);
			}
			return val;
		},
		set : newVal=>{
			var value = val;
			if(value===newVal)return;
			val = newVal;//set设置那个新值
			childOb = observe(newVal);//让子属性都有那个set方法
			dep.notify();//发出通知
		}
	})
}
function observe(val,vm){//
	if(!val||typeof val!=='object')return;
	return new Observer(val);
}

/**
 * 代码很简单，就给每个属性（包括子属性）
 * 都加上get/set，这样的话，这个对象的，
 * 有任何赋值，就会触发set方法。。
 * 所以，我们是不是应该写一个消息－订阅器呢？
 */

//我们用一个数组，用来存放那个订阅者，一旦有订阅者就触发notify()通知
class Dep{//订阅类
	constructor(){
		this.subs = [];
	}
	addSub(sub){
		this.subs.push(sub);//添加订阅者
	}
	notify(){
		this.subs.forEach(sub=>sub.update());//当notify()发出通知时，
		//就触发update()数据更新函数
	}

}

/**
 * 那么问题来了。
 * 谁是订阅者。对，是Watcher。
 * 一旦 dep.notify()就遍历订阅者，
 * 也就是Watcher，并调用他的update()方法
 */
/**
 * 3. 实现一个Watcher
 * 我们想象这个Watcher，应该用什么东西。
 * update方法，嗯这个毋庸置疑，还有呢。
 * v.$watch("a",()=>console.log("哈哈，$watch成功"))
 * 对表达式（就是那个“a”） 和 回调函数，这是最基本的，所以我们简单写写
 */
class Watcher{
	constructor(vm,expOrFn,callback){
		this.vm = vm;
		this.callback = callback;
		this.expOrFn = expOrFn;
		this.value = this.get();
	}
	update(){
		this.run();
	}
	run(){
		const value = this.get();//获取此时的数据
		if(value!==this.value){
			//检测此时的值与原来的值是否相等
			this.value = value;//检测到值变化
			this.callback.call(this.vm);//执行回调函数
		}
	}
	get(){
		Dep.target = this;
		const value = this.vm._data[this.expOrFn];
		Dep.target = null;
		return value;
	}
}
/**
 * 那么问题来了，我们怎样将通过addSub(),将Watcher加进去呢。
 * 我们发现var dep = new Dep() 处于闭包当中，
 * 我们又发现Watcher的构造函数里会调用this.get，
 * 所以，我们可以在上面动动手脚，
 * 修改一下Object.defineProperty的get要调用的函数，
 * 判断是不是Watcher的构造函数调用，
 * 如果是，说明他就是这个属性的订阅者，果断将他addSub()中去，
 * 那问题来了？
 * 我怎样判断他是Watcher的this.get调用的，而不是我们普通调用的呢。
 */
//对，在Dep定义一个全局唯一的变量，跟着思路我们写一下
/**
 * 4. 实现一个 Vue
 * 还差一步就大功告成了，
 * 我们要把以上代码配合Vue的$watch方法来用，
 * 要watch Vue实例的属性，算了，不要理会我在说什么，直接看代码吧
 */

class Vue{
	constructor(options={}){
		this.$options = options;//将传进来的参数进行处理
		let data = this._data=this.$options.data
 		Object.keys(data).forEach(key=>this._proxy(key))
 		observe(data,this);//让传进来数据得到那个set方法的检测
	}
	$watch(expOrFn, callback){
 		new Watcher(this, expOrFn, callback)
 	}
 	//demo._data.text没有demo.text用着爽，没关系，我们加一个代理
	 _proxy(key) {
	 	var self = this
	 	Object.defineProperty(self, key, {
	 		configurable: true,
	 		enumerable: true,
	 		get: function proxyGetter () {
	 			return self._data[key]
	 		},
	 		set: function proxySetter (val) {
	 			self._data[key] = val
	 		}
	 	})
	}
}